Milestone 1
11 Sep 2024Acquisition de données
Question 1 :
Description du paquet
Ce paquet est responsable de la récupération et du stockage des données provenant de l’API de la LNH.
Il utilise la classe ApiClient pour récupérer les données et la classe FileSystemCache pour les stocker.
Il stocke deux types de données : le cache et le dump.
Lors de la récupération des données, le cache est utilisé pour stocker les données temporairement. Cela permet d’exécuter le processus de récupération en plusieurs étapes sans avoir à récupérer les mêmes données plusieurs fois. Le cache est stocké dans le dossier ift6758/data/storage/cache. Ce chemin peut être modifié avec la variable d’environnement CACHE_PATH.
Une fois les données d’une saison récupérées, elles sont stockées dans le dump. À la fin du processus de récupération, il est conseillé de vider le cache pour libérer de l’espace. Le dump est stocké dans le dossier ift6758/data/storage/dump. Ce chemin peut être modifié avec la variable d’environnement DUMP_PATH.
Utilisation : Récupérer les données depuis l’API
from ift6758.data import fetch_all_seasons_games_data
fetch_all_seasons_games_data()
Cela récupérera tous les matchs depuis l’API de la LNH et les stockera dans ift6758/data/storage/dump.
Si vous devez libérer de l’espace, il peut être nécessaire de vider le cache :
from ift6758.data import clear_cache
clear_cache()
Vous pouvez également supprimer le dossier ift6758/data/storage/cache.
Charger les données aplaties dans un DataFrame
Cela chargera les données depuis le dump et les aplatis dans un DataFrame.
from ift6758.data import load_events_dataframe
df_2020 = load_events_dataframe(2020)
df_all = load_events_dataframe()
Utilisation avancée
from ift6758.data import (ApiClient, FileSystemCache, DataTransformer, GameType)
import os
import json
cache_path = os.path.dirname(os.path.abspath(__file__)) + "/storage/cache"
cache = FileSystemCache(cache_path)
dump_path = os.path.dirname(os.path.abspath(__file__)) + "/storage/dump"
dump = FileSystemCache(dump_path)
client = ApiClient(cache)
data_transformer = DataTransformer()
data = client.get_games_data(2020, [GameType.REGULAR, GameType.PLAYOFF])
dump.set("2020", json.dumps(data, indent=2))
df = data_transformer.flatten_raw_data_as_dataframe(data)
records = data_transformer.flatten_raw_data_as_records(data)
Outil de débogage interactif
Question 1 :
Capture d’écran
Code de l’outil
import os
import json
import ipywidgets as widgets
from IPython.display import clear_output, display
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
data_folder = '../ift6758/data/storage/dump/'
rink_image_path = '../figures/nhl_rink.png'
rink_width = 800
rink_height = 400
rink_x_min, rink_x_max = -100, 100
rink_y_min, rink_y_max = -42.5, 42.5
def load_season_data(season):
file_path = os.path.join(data_folder, f"{season}.json")
with open(file_path, 'r') as f:
data = json.load(f)
return data
def filter_games_by_type(data, season_type):
"""
Filters the game data by regular season ('02') or playoffs ('03').
It checks the 5th and 6th characters of the game ID to determine the game type.
"""
filtered_games = [game for game in data if str(game['id'])[4:6] == season_type]
return filtered_games
def display_event_info(game_data, event_index):
event_data = game_data['plays'][event_index]
event_id = event_data['eventId']
period = event_data['periodDescriptor']['number']
time_in_period = event_data['timeInPeriod']
event_type = event_data['typeDescKey']
with event_output:
clear_output(wait=True)
print(f"Event ID: {event_id}")
print(f"Period: {period}")
print(f"Time in Period: {time_in_period}")
print(f"Event Type: {event_type}")
if 'details' in event_data:
details = event_data['details']
if 'xCoord' in details and 'yCoord' in details:
print(f"Event Position: x={details['xCoord']}, y={details['yCoord']}")
transformed_x, transformed_y = transform_coordinates(details['xCoord'], details['yCoord'])
display_rink_image(transformed_x, transformed_y)
else:
display_rink_image()
if 'reason' in details:
print(f"Reason: {details['reason']}")
if 'winningPlayerId' in details:
print(f"Winning Player ID: {details['winningPlayerId']}")
if 'losingPlayerId' in details:
print(f"Losing Player ID: {details['losingPlayerId']}")
if 'shootingPlayerId' in details:
print(f"Shooting Player ID: {details['shootingPlayerId']}")
if 'goalieInNetId' in details:
print(f"Goalie in Net ID: {details['goalieInNetId']}")
if 'hittingPlayerId' in details:
print(f"Hitting Player ID: {details['hittingPlayerId']}")
if 'hitteePlayerId' in details:
print(f"Hittee Player ID: {details['hitteePlayerId']}")
if 'blockingPlayerId' in details:
print(f"Blocking Player ID: {details['blockingPlayerId']}")
else:
display_rink_image()
print("\nJSON:")
print(json.dumps(event_data, indent=4))
def display_game_info(season, game_index):
data = load_season_data(season)
season_type = season_type_selector.value
filtered_game_data = filter_games_by_type(data, season_type)
game_data = filtered_game_data[game_index]
with match_output:
clear_output(wait=True)
print(f"Game ID: {game_data['id']}")
print(f"Date: {game_data['gameDate']}")
print(f"Home Team: {game_data['homeTeam']['name']['default']} (Score: {game_data['homeTeam']['score']})")
print(f"Away Team: {game_data['awayTeam']['name']['default']} (Score: {game_data['awayTeam']['score']})")
print(f"Venue: {game_data['venue']['default']} - {game_data['venueLocation']['default']}")
print(f"Start Time (UTC): {game_data['startTimeUTC']}")
event_slider.max = len(game_data['plays']) - 1
event_slider.value = 0
def update_event_output(*args):
display_event_info(game_data, event_slider.value)
event_slider.observe(update_event_output, names='value')
with slider_output:
clear_output(wait=True)
display(event_slider)
def transform_coordinates(x, y):
transformed_x = ((x - rink_x_min) / (rink_x_max - rink_x_min)) * rink_width
transformed_y = rink_height - ((y - rink_y_min) / (rink_y_max - rink_y_min) * rink_height)
return transformed_x, transformed_y
def display_rink_image(xCoord=None, yCoord=None):
fig, ax = plt.subplots(figsize=(8, 4))
img = mpimg.imread(rink_image_path)
ax.imshow(img, extent=[0, rink_width, 0, rink_height])
if xCoord is not None and yCoord is not None:
ax.plot(xCoord, yCoord, 'go', markersize=8, label="Event Position")
ax.legend()
ax.set_xlim(0, rink_width)
ax.set_ylim(rink_height, 0)
ax.axis('off')
plt.show()
season_selector = widgets.Dropdown(
options=[2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023],
description='Saison:'
)
season_type_selector = widgets.Dropdown(
options={'All': 'all', 'Regular Season': '02', 'Playoffs': '03'},
description='Season Type:'
)
game_slider = widgets.IntSlider(
min=0,
max=100,
step=1,
description='Match:'
)
event_slider = widgets.IntSlider(
min=0,
max=10,
step=1,
description='Event:'
)
def update_game_slider(*args):
season = season_selector.value
season_type = season_type_selector.value
data = load_season_data(season)
filtered_data = filter_games_by_type(data, season_type) if season_type != 'all' else data
game_slider.max = len(filtered_data) - 1
game_slider.value = 0
def update_game_output(*args):
display_game_info(season_selector.value, game_slider.value)
season_selector.observe(update_game_slider, names='value')
season_type_selector.observe(update_game_slider, names='value')
game_slider.observe(update_game_output, names='value')
match_output = widgets.Output()
slider_output = widgets.Output()
event_output = widgets.Output()
Nettoyer les données
Question 1 :
Capture d’écran de notre Dataframe
Question 2 :
Si le champ de “force” n’existait pas pour les tirs, une manière de le savoir serait de prendre en compte les événements de type “penality”. En effet, en regardant les temps auquels les pénalités sont prises et leurs durées, on peut facilement déduire si un tir a été effectué à force égale, en infériorité ou en supériorité numérique. Une information qui nous est importante pour faire la déduction et le temps auquel le tir à été fait et nous l’avons.
Question 3 :
Caractéristique 1 : Rebond
On pourrait qualifier de rebond tout tir qui est effectué moins de 2 secondes après un premier tir. Évedemment les deux tirs doivent être sur le même gardien. Les rebonds sont souvent des tirs qui ont plus de chances de rentrer dans le but donc il est intéressant de les analyser.
Caractéristique 2 : Tir après mise au jeu
On pourrait qualifier de tir après mise au jeu tout tir qui est effectué moins de 3 secondes après une mise au jeu. Ainsi, il serait possible d’analyser quels sont les éléments nécéssaires pour marquer un but directement après la mise au jeu.
Caractéristique 3 : Tir de pénalité
On pourrait qualifier de tir de pénalité tout tir qui est effectué avec un “situationCode” de 1010 ou de 0101 et si la période actuelle est inférieure à 5. En effet, le situation code indique que c’est seulement un tireur contre un gardien sur la glace et la période inférieure à 5 indique que ce n’est pas un tir de barrage mais bien un tir de pénalité (durant une des trois périodes ou durant la prolongation).
Visualisations simples
Question 1
Graphique qui compare les types de tirs de toutes les équipes dans une saison et discussion.
a)
Un graphique qui montre la relation entre la distance à laquelle un tir est fait et la chance qu’il soit un but. Ce graphique est pour toutes les saisons entre 2018-19 et 2020-21. Discussion.
Question 2
Combiner les deux graphiques ci-dessus en un nouveau graphique et discussion.
Visualisations avancées
Question 1
4 graphiques de zone offensive qui permettent de sélectionner n’importe quelle équipe pour la saison.
Question 2
Discussion
Question 3
Discussion par rapport à l’écolution de l’Avalanche du Colorado.
Question 4
Discussion par rapport aux Sabres de Buffalo et au Lightning de Tampa Bay.